//
//  XLPlayerDecoder.m
//  FFMpegKitDemo
//
//  Created by ilongge on 2023/8/2.
//

#import "XLPlayerDecoder.h"
#import "XLPlayerError.h"
#import "XLPlayerConfig.h"

#import "XLPlayerAudioFrame.h"
#import "XLPLayerVideoYUVFrame.h"

#import <libavformat/avformat.h>
#import <libavcodec/avcodec.h>
#import <libavutil/avutil.h>
#import <libswscale/swscale.h>
#import <libswresample/swresample.h>

#define Audio_Sample_Rate    48000

#define Audio_Sample_Format  AV_SAMPLE_FMT_S16

@interface XLPlayerDecoder()
{
    AVFormatContext *_formatContext;
    
    AVCodecContext *_videoCodecContext;
    struct SwsContext *_videoSwsctx;
    int _videoStreamIndex;
    AVFrame *_videoFrame;
    
    AVCodecContext *_audioCodecContext;
    SwrContext *_audioSwrctx;
    int _audioStreamIndex;
    AVFrame *_audioFrame;
    AVChannelLayout _outChannelLayout;
}

@end

@implementation XLPlayerDecoder

+(instancetype)decoderWithFormatContxt:(AVFormatContext *)formatContext
                                config:(XLPlayerConfig *)config
                      videoStreamIndex:(int)videoStreamIndex
                      audioStreamIndex:(int)audioStreamIndex
                                 error:(NSError *__autoreleasing *)error
{
    XLPlayerDecoder *decoder = [[XLPlayerDecoder alloc] initWithFormatContxt:formatContext
                                                                      config:config
                                                            videoStreamIndex:videoStreamIndex
                                                            audioStreamIndex:audioStreamIndex
                                                                       error:error];
    return decoder;
}

- (instancetype)initWithFormatContxt:(AVFormatContext *)formatContext
                              config:(XLPlayerConfig *)config
                    videoStreamIndex:(int)videoStreamIndex
                    audioStreamIndex:(int)audioStreamIndex
                               error:(NSError *__autoreleasing *)error
{
    if (formatContext == NULL) {
        *error = [XLPlayerError errorWithCode:XLPlayerErrorCodeFormatContextNull];
        return nil;
    }
    self = [super init];
    if (self) {
        if (config == nil) {
            config = [[XLPlayerConfig alloc] init];
        }
        _config = config;
        _formatContext = formatContext;
        _videoStreamIndex = videoStreamIndex;
        _audioStreamIndex = audioStreamIndex;
        _videoCodecContext = [self createVideoDecoder];
        if (_videoCodecContext == NULL) {
            *error = [XLPlayerError errorWithCode:XLPlayerErrorCodeCreateVideoCodecFailed];
            return nil;
        }
        _audioCodecContext = [self createAudioDecoder];
        if (_audioCodecContext == NULL) {
            *error = [XLPlayerError errorWithCode:XLPlayerErrorCodeCreateAudioCodecFailed];
            return nil;
        }
    }
    return self;
}

- (void)decoderVideoPacket:(AVPacket)packet frame:(AVFrame *)frame yuvFrame:(AVFrame *)yuvFrame
{
    avcodec_send_packet(_videoCodecContext, &packet);
    avcodec_receive_frame(_videoCodecContext, frame);
    sws_scale_frame(_videoSwsctx,
                    frame,
                    yuvFrame);
}

- (void)decoderAudioPacket:(AVPacket)packet frame:(AVFrame *)frame swrFrame:(AVFrame *)swrFrame
{
    avcodec_send_packet(_audioCodecContext, &packet);
    avcodec_receive_frame(_audioCodecContext, frame);
    swrFrame->sample_rate = Audio_Sample_Rate;
    swrFrame->format = Audio_Sample_Format;
    swrFrame->ch_layout = _outChannelLayout;
    swrFrame->pts = frame->pts;
    swrFrame->pkt_dts = frame->pkt_dts;
    swrFrame->pkt_pos = frame->pkt_pos;
    swrFrame->pkt_duration = frame->pkt_duration;
    swrFrame->pkt_size = frame->pkt_size;
    int ret = swr_convert_frame(_audioSwrctx,
                                swrFrame,
                                frame);
    
    int bytePerSample = av_get_bytes_per_sample(_audioCodecContext->sample_fmt);
    NSInteger length = packet.size * swrFrame->ch_layout.nb_channels * sizeof(float);
    NSData *audioData = [[NSMutableData alloc] initWithBytes:swrFrame->data length:length];
    XLPlayerAudioFrame *audioFrame = [[XLPlayerAudioFrame alloc] init];
    audioFrame.data = audioData;
    audioFrame.pts = frame->pts;
    audioFrame.sampleRate = frame->sample_rate;
    audioFrame.channel = frame->ch_layout.nb_channels;
    audioFrame.size = frame->pkt_size;
    float duration = (float)audioFrame.data.length / (sizeof(float) * swrFrame->ch_layout.nb_channels * swrFrame->sample_rate);
}

#pragma mark -------------------------

- (AVCodecContext *)createVideoDecoder
{
    AVCodecContext *codecContext = NULL;
    if (_config.videoToolbox) {
        codecContext = [self openHardwareVideoCodec:_formatContext streamIndex:_videoStreamIndex];
        if (codecContext == NULL) {
            _config.videoToolbox = NO;
            NSLog(@"视频硬件解码器创建失败，将使用软件解码器");
        }
    }
    if (codecContext == NULL) {
        codecContext = [self openSoftwareVideoCodec:_formatContext streamIndex:_videoStreamIndex];
        if (codecContext == NULL) {
            NSLog(@"视频软件解码器创建失败");
            return NULL;
        }
    }
    if (codecContext) {
        _videoSwsctx = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
                                      codecContext->width, codecContext->height, AV_PIX_FMT_YUV420P,
                                      SWS_BICUBIC, NULL, NULL, NULL);
    }
    return codecContext;
}

- (AVCodecContext *)createAudioDecoder
{
    AVCodecContext *codecContext = NULL;
    codecContext = [self openAudioCodec:_formatContext streamIndex:_audioStreamIndex];
    if (codecContext == NULL) {
        NSLog(@"音频解码器创建失败");
        return NULL;
    }
    _audioSwrctx = swr_alloc();
    //初始化重采样 采样率为双通 short， 44100
    AVChannelLayout inChannelLayout;
    inChannelLayout.nb_channels = _formatContext->streams[_audioStreamIndex]->codecpar->ch_layout.nb_channels;
    
    _outChannelLayout.nb_channels = 2;
    
    swr_alloc_set_opts2(&_audioSwrctx,
                        &_outChannelLayout,
                        Audio_Sample_Format,
                        Audio_Sample_Rate,
                        &inChannelLayout,
                        codecContext->sample_fmt,
                        codecContext->sample_rate,
                        0,
                        NULL);
    return codecContext;
}

- (AVCodecContext *)openSoftwareVideoCodec:(AVFormatContext *)formatContext streamIndex:(int)streamIndex {
    if (streamIndex == -1) {
        return NULL;
    }
    AVCodecParameters *params = formatContext->streams[streamIndex]->codecpar;
    const AVCodec *codec = avcodec_find_decoder(params->codec_id);
    if (codec == NULL) {
        return NULL;
    }
    AVCodecContext *context = avcodec_alloc_context3(codec);
    if (context == NULL) {
        return NULL;
    }
    int ret = avcodec_parameters_to_context(context, params);
    if (ret < 0) {
        avcodec_free_context(&context);
        return NULL;
    }
    ret = avcodec_open2(context, codec, NULL);
    if (ret < 0) {
        avcodec_free_context(&context);
        return NULL;
    }
    return context;
}

- (AVCodecContext *)openHardwareVideoCodec:(AVFormatContext *)formatContext streamIndex:(int)streamIndex
{
    if (streamIndex == -1) {
        return NULL;
    }
    const AVCodec *videoCodec = avcodec_find_decoder(formatContext->streams[streamIndex]->codecpar->codec_id);
    // 为解码器上下文对象分配内存
    AVCodecContext *codecContext = avcodec_alloc_context3(videoCodec);
    if (!codecContext){
        return NULL;
    }
    // 将视频流中的参数填充到视频解码器中
    int ret = avcodec_parameters_to_context(codecContext, formatContext->streams[streamIndex]->codecpar);
    if (ret < 0){
        return NULL;
    }
    // 指定解码器名称, 这里使用苹果VideoToolbox中的硬件解码器
    const char *codecName = av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_VIDEOTOOLBOX);
    // 将解码器名称转为对应的枚举类型
    enum AVHWDeviceType device_type = av_hwdevice_find_type_by_name(codecName);
    if (device_type != AV_HWDEVICE_TYPE_VIDEOTOOLBOX) {
        return NULL;
    }
    AVBufferRef *hw_device_ctx;
    ret = av_hwdevice_ctx_create(&hw_device_ctx, device_type, NULL, NULL, 0);
    if (ret < 0) {
        av_buffer_unref(&hw_device_ctx);
        return NULL;
    }
    codecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx);
    if (ret < 0){
        av_buffer_unref(&hw_device_ctx);
        return NULL;
    }
    // 初始化解码器上下文对象
    ret = avcodec_open2(codecContext, videoCodec, NULL);
    if (ret < 0) {
        av_buffer_unref(&hw_device_ctx);
        return NULL;
    }
    return codecContext;
}

- (AVCodecContext *)openAudioCodec:(AVFormatContext *)formatContext streamIndex:(int)streamIndex {
    if (streamIndex == -1) {
        return NULL;
    }
    AVCodecParameters *params = formatContext->streams[streamIndex]->codecpar;
    const AVCodec *codec = avcodec_find_decoder(params->codec_id);
    if (codec == NULL) {
        return NULL;
    }
    
    AVCodecContext *context = avcodec_alloc_context3(codec);
    if (context == NULL) {
        return NULL;
    }
    
    int ret = avcodec_parameters_to_context(context, params);
    if (ret < 0) {
        avcodec_free_context(&context);
        return NULL;
    }
    ret = avcodec_open2(context, codec, NULL);
    if (ret < 0) {
        avcodec_free_context(&context);
        return NULL;
    }
    return context;
}

@end
